package com.yh.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.yh.config.OtherSource;
import com.yh.dao.DirectorDao;
import com.yh.entity.Director;
import com.yh.entity.Movie;
import com.yh.service.ElasticsearchService;
import com.yh.utils.Constants;
import com.yh.vo.CustomPageAndNavigate;
import com.yh.vo.ScreenVO;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.log4j.Logger;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yuhuan
 * @date 2022/01/13
 */
@DubboService
public class ElasticsearchServiceImpl implements ElasticsearchService {
    /**
     * 日志
     */
    private static final Logger logger = Logger.getLogger(ElasticsearchServiceImpl.class);

    /**
     * 记录初始化执行的次数
     */
    private int initFrequency = 0;
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private OtherSource otherSource;
    @Autowired
    private DirectorDao directorDao;

    @Override
    public boolean autoInitIndex() {
        this.initFrequency = 0;
        return initIndex();
    }

    /**
     * 开始执行初始化程序
     */
    public boolean initIndex() {
        try {
            createIndex();
            createMappingByJson();
            return true;
        } catch (Exception e) {
            boolean result = false;
            // 重新初始化时，确保只尝试执行一次
            if (this.initFrequency < 1) {
                deleteIndex();
                result = initAgain(this);
            }
            if (!result) {
                logger.error(">>>>> 在索引初始化时引发失败！" + e.toString());
            }
            return result;
        }
    }

    /**
     * 尝试重新执行初始化程序
     */
    private boolean initAgain(ElasticsearchServiceImpl elasticsearchService) {
        this.initFrequency++;
        return elasticsearchService.initIndex();
    }

    @Override
    public boolean deleteIndex() {
        DeleteIndexRequest deleteIndex = new DeleteIndexRequest(Constants.ElasticsearchData.INDEX_NAME);
        try {
            AcknowledgedResponse response = client.indices().delete(deleteIndex, RequestOptions.DEFAULT);
            boolean acknowledged = response.isAcknowledged();
            if (acknowledged) {
                logger.debug(">>>>> 索引删除成功！");
                return true;
            }
            logger.debug(">>>>> 在索引删除时失败！");
        } catch (Exception e) {
            logger.debug(">>>>> 删除索引时出现异常！" + e.toString());
        }
        return false;
    }

    @Override
    public void createDocument(Movie movie) {
        /** 创建请求对象 */
        IndexRequest indexRequest = new IndexRequest(Constants.ElasticsearchData.INDEX_NAME)
                .id(String.valueOf(movie.getMovieId())).source(extractEssentialInformation(movie));
        // 执行请求
        try {
            client.index(indexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            logger.error(">>>>> 创建文档时出现异常" + e.toString());
        }
    }

    @Override
    public void deleteDocument(int movieId) {
        /** 创建删除请求对象 */
        DeleteRequest deleteRequest = new DeleteRequest(Constants.ElasticsearchData.INDEX_NAME)
                .id(String.valueOf(movieId));
        // 执行删除请求
        try {
            client.delete(deleteRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            logger.error(">>>>> 删除文档时出现异常" + e.toString());
        }
    }

    @Override
    public void updateDocument(Movie movie) {
        /** 创建修改请求对象 */
        UpdateRequest updateRequest = new UpdateRequest(Constants.ElasticsearchData.INDEX_NAME, String.valueOf(movie.getMovieId()))
                .doc(extractEssentialInformation(movie));
        // 执行修改请求
        try {
            client.update(updateRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            logger.error(">>>>> 更改文档时出现异常" + e.toString());
        }
    }

    @Override
    public CustomPageAndNavigate searchOfKeyWord(String keyWord, int pageNum, int pageSize) {
        // 读取检索字段
        String[] fields = otherSource.fieldNames.split(",");
        if (ObjectUtils.isEmpty(fields)) {
            logger.debug(">>>>> 检索失败！原因是没有设置检索字段");
        }
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        try {
            // 如果关键字触发了查询所有的条件，就查询所有影片
            if (keyWord.equals(Constants.MovieData.OFF_KEY_WORD)) {
                List<Map<String, Object>> list = searchAllOrMultiMatch(sourceBuilder);
                return new CustomPageAndNavigate(pageNum, pageSize, list);
            }
            // 设置搜索条件
            SearchSourceBuilder builder = sourceBuilder.query(QueryBuilders.multiMatchQuery(keyWord, fields));
            List<Map<String, Object>> list = searchAllOrMultiMatch(builder);
            return new CustomPageAndNavigate(pageNum, pageSize, list);
        } catch (IOException e) {
            logger.error(">>>>> 检索失败！请确保搜索引擎工作正常" + e.toString());
        }
        return null;
    }

    @Override
    public CustomPageAndNavigate searchByTypeAndCountryAndYear(ScreenVO screenVO) {
        // 默认查询所有数据
        boolean isSearchAll = true;
        // 生成搜索源
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 编辑搜索条件
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if (!screenVO.getMovieType().equals(Constants.MovieData.OFF_KEY_WORD)) {
            /** 设置影片类型 */
            boolQueryBuilder.must(QueryBuilders.termQuery("movieType", screenVO.getMovieType()));
            isSearchAll = false;
        }
        if (!screenVO.getMovieCountry().equals(Constants.MovieData.OFF_KEY_WORD)) {
            /** 指定影片的制作区域 */
            boolQueryBuilder.must(QueryBuilders.termQuery("movieCountry", screenVO.getMovieCountry()));
            isSearchAll = false;
        }
        if (!screenVO.getYearStr().equals(Constants.MovieData.OFF_KEY_WORD)) {
            /** 设置影片的上映年份 */
            boolQueryBuilder.must(QueryBuilders.termQuery("movieReleaseYear", screenVO.getYearStr()));
            isSearchAll = false;
        }
        // 如果是筛选搜索，就将搜索源替换为带有搜索条件的搜索源
        if (!isSearchAll) {
            sourceBuilder = sourceBuilder.query(boolQueryBuilder);
        }
        /** 设置排序规则 'ASC'表示'升序' 'DESC'表示'降序' */
        if (screenVO.getReorder() == Constants.MovieData.REORDER_OF_TIME) {
            // 按时间排序
            sourceBuilder.sort(Constants.MovieData.REORDER_OF_TIME_IN_FIELD, SortOrder.DESC);
        }
        if (screenVO.getReorder() == Constants.MovieData.REORDER_OF_SCORE) {
            // 按影片评分排序
            sourceBuilder.sort(Constants.MovieData.REORDER_OF_SCORE_IN_FIELD, SortOrder.DESC);
        }
        try {
            // 执行条件筛选
            List<Map<String, Object>> list = searchForBool(sourceBuilder);
            // 分页
            return new CustomPageAndNavigate(screenVO.getPageNum(), screenVO.getPageSize(), list);
        } catch (IOException e) {
            logger.error(">>>>> 检索失败！请确保搜索引擎工作正常" + e.toString());
        }
        return null;
    }

    /**
     * 多字段检索
     *
     * @searchAll 表示是否查询所有数据，如果查询所有数据；那么将不会使用另外的两个参数
     * @keyWord 进行多字段检索的关键字
     * @fields 需要进行检索的字段
     */
    public List<Map<String, Object>> searchAllOrMultiMatch(SearchSourceBuilder builder) throws IOException {
        List<Map<String, Object>> movies = new ArrayList<>();
        SearchRequest searchRequest = new SearchRequest(Constants.ElasticsearchData.INDEX_NAME);
        searchRequest.source(builder);
        // 向搜索引擎发起搜索请求
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        if (searchResponse.getHits().getHits().length == 0) {
            return movies;
        }
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            movies.add(hit.getSourceAsMap());
        }
        return movies;
    }

    /**
     * 组合筛选查询
     *
     * @searchAll 表示是否查询所有数据
     * @builder 检索条件
     */
    public List<Map<String, Object>> searchForBool(SearchSourceBuilder builder) throws IOException {
        List<Map<String, Object>> movies = new ArrayList<>();
        SearchRequest searchRequest = new SearchRequest(Constants.ElasticsearchData.INDEX_NAME);
        searchRequest.source(builder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        if (searchResponse.getHits().getHits().length == 0) {
            logger.debug(">>>>> 没有找到任何影片信息");
            return movies;
        }
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            movies.add(hit.getSourceAsMap());
        }
        return movies;
    }

    /**
     * 创建索引
     */
    public void createIndex() throws IOException {
        // 索引创建请求
        CreateIndexRequest createIndex = new CreateIndexRequest(Constants.ElasticsearchData.INDEX_NAME);
        /** 通过客户端发送请求创建索引
         * @indices() 提供有关索引的API方法 */
        CreateIndexResponse response = client.indices().create(createIndex, RequestOptions.DEFAULT);
        // 查看响应是否被确认
        boolean acknowledged = response.isAcknowledged();
        if (acknowledged) {
            logger.debug(">>>>> 索引创建成功！");
        }
    }

    /**
     * 创建映射 - 原始JSON格式
     */
    public void createMappingByJson() throws IOException {
        /** 映射添加请求 */
        PutMappingRequest putMappingRequest = new PutMappingRequest(Constants.ElasticsearchData.INDEX_NAME);
        // 配置映射
        //language=JSON
        String mappingSource = "{\n" +
                "  \"properties\": {\n" +
                "    \"movieId\": {\n" +
                "      \"type\":\"integer\"\n" +
                "    },\n" +
                "    \"movieCnName\": {\n" +
                "      \"type\":\"keyword\"\n" +
                "    },\n" +
                "    \"movieActor\": {\n" +
                "      \"type\":\"text\",\n" +
                "      \"analyzer\":\"ik_smart\"\n" +
                "    },\n" +
                "    \"movieDirector\": {\n" +
                "      \"type\":\"keyword\"\n" +
                "    },\n" +
                "    \"movieType\": {\n" +
                "      \"type\":\"text\",\n" +
                "      \"analyzer\":\"ik_smart\"\n" +
                "    },\n" +
                "    \"movieScore\": {\n" +
                "      \"type\":\"double\"\n" +
                "    },\n" +
                "    \"moviePicture\": {\n" +
                "      \"type\":\"text\"\n" +
                "    },\n" +
                "    \"movieReleaseYear\": {\n" +
                "      \"type\":\"integer\"\n" +
                "    },\n" +
                "    \"movieCountry\": {\n" +
                "      \"type\":\"text\",\n" +
                "      \"analyzer\":\"ik_max_word\"\n" +
                "    }\n" +
                "  }\n" +
                "}";
        /** 将映射的配置添加到请求体 */
        putMappingRequest.source(mappingSource, XContentType.JSON);
        // 发送请求创建映射
        AcknowledgedResponse response = client.indices().putMapping(putMappingRequest, RequestOptions.DEFAULT);
        if (response.isAcknowledged()) {
            logger.debug(">>>>> 映射创建成功！");
        }
    }

    /**
     * 要点信息摘录
     */
    private Map<String, Object> extractEssentialInformation(Movie movie) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("movieId", movie.getMovieId());
        map.put("movieCnName", movie.getMovieCnName());
        map.put("movieActor", movie.getMovieActor());
        // 找到导演的信息
        Director director = directorDao.findDirectorById(movie.getMovieDirectorId());
        if (!ObjectUtils.isEmpty(director)) {
            map.put("movieDirector", director.getDirectorName());
        }
        // 处理影片的发行年份
        DateTime dateTime = DateTime.of(movie.getMovieReleaseDate());
        map.put("movieReleaseYear", DateUtil.year(dateTime));
        map.put("movieType", movie.getMovieType());
        map.put("movieScore", movie.getMovieScore());
        map.put("moviePicture", movie.getMoviePicture());
        map.put("movieCountry", movie.getMovieCountry());
        return map;
    }
}
